/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
/**********************************************************************
	UI_QMENU.C
 
	Quake's menu framework system.
**********************************************************************/
#include "ui_local.h"

sfxHandle_t menu_in_sound;
sfxHandle_t menu_move_sound;
sfxHandle_t menu_out_sound;
sfxHandle_t menu_buzz_sound;
sfxHandle_t menu_null_sound;
sfxHandle_t weaponChangeSound;

static qhandle_t	sliderBar;
static qhandle_t	sliderButton_0;
static qhandle_t	sliderButton_1;

vec4_t menu_text_color	    = {1.0f, 1.0f, 1.0f, 1.0f};
vec4_t menu_dim_color       = {0.0f, 0.0f, 0.0f, 0.75f};
vec4_t color_black	    = {0.00f, 0.00f, 0.00f, 1.00f};
vec4_t color_white	    = {1.00f, 1.00f, 1.00f, 1.00f};
vec4_t color_yellow	    = {1.00f, 1.00f, 0.00f, 1.00f};
vec4_t color_blue	    = {0.00f, 0.00f, 1.00f, 1.00f};
vec4_t color_lightOrange    = {1.00f, 0.68f, 0.00f, 1.00f };
vec4_t color_orange	    = {1.00f, 0.43f, 0.00f, 1.00f};
vec4_t color_red	    = {1.00f, 0.00f, 0.00f, 1.00f};
vec4_t color_dim	    = {0.00f, 0.00f, 0.00f, 0.25f};

// current color scheme
vec4_t pulse_color          = {1.00f, 1.00f, 1.00f, 1.00f};
vec4_t text_color_disabled  = {0.50f, 0.50f, 0.50f, 1.00f};	// light gray
vec4_t text_color_normal    = {1.00f, 0.43f, 0.00f, 1.00f};	// light orange
vec4_t text_color_highlight = {1.00f, 1.00f, 0.00f, 1.00f};	// bright yellow
vec4_t listbar_color        = {1.00f, 0.43f, 0.00f, 0.30f};	// transluscent orange
vec4_t text_color_status    = {1.00f, 1.00f, 1.00f, 1.00f};	// bright white

// action widget
static void	Action_Init( menuaction_s *a );
static void	Action_Draw( menuaction_s *a );

// radio button widget
static void	RadioButton_Init( menuradiobutton_s *rb );
static void	RadioButton_Draw( menuradiobutton_s *rb );
static sfxHandle_t RadioButton_Key( menuradiobutton_s *rb, int key );

// slider widget
static void Slider_Init( menuslider_s *s );
static sfxHandle_t Slider_Key( menuslider_s *s, int key );
static void	Slider_Draw( menuslider_s *s );

// spin control widget
static void	SpinControl_Init( menulist_s *s );
static void	SpinControl_Draw( menulist_s *s );
static sfxHandle_t SpinControl_Key( menulist_s *l, int key );

// text widget
static void Text_Init( menutext_s *b );
static void Text_Draw( menutext_s *b );

// scrolllist widget
static void	ScrollList_Init( menulist_s *l );
sfxHandle_t ScrollList_Key( menulist_s *l, int key );

// proportional text widget
static void PText_Init( menutext_s *b );
static void PText_Draw( menutext_s *b );

// proportional banner text widget
static void BText_Init( menutext_s *b );
static void BText_Draw( menutext_s *b );

/*
=================
Text_Init
=================
*/
static void Text_Init( menutext_s *t )
{
    t->generic.flags |= QMF_INACTIVE;
}

/*
=================
Text_Draw
=================
*/
static void Text_Draw( menutext_s *t )
{
    int		x;
    int		y;
    char	buff[512];
    float*	color;

    x = t->generic.x;
    y = t->generic.y;

    buff[0] = '\0';

    // possible label
    if (t->generic.name)
        strcpy(buff,t->generic.name);

    // possible value
    if (t->string)
        strcat(buff,t->string);

    if (t->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else
        color = t->color;

    UI_DrawString( x, y, buff, t->style, color );
}

/*
=================
BText_Init
=================
*/
static void BText_Init( menutext_s *t )
{
    t->generic.flags |= QMF_INACTIVE;
}

/*
=================
BText_Draw
=================
*/
static void BText_Draw( menutext_s *t )
{
    int		x;
    int		y;
    float*	color;

    x = t->generic.x;
    y = t->generic.y;

    if (t->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else
        color = t->color;

    UI_DrawBannerString( x, y, t->string, t->style, color );
}

/*
=================
PText_Init
=================
*/
static void PText_Init( menutext_s *t )
{
    int	x;
    int	y;
    int	w;
    int	h;
    float	sizeScale;

    sizeScale = UI_ProportionalSizeScale( t->style );

    x = t->generic.x;
    y = t->generic.y;
    w = UI_ProportionalStringWidth( t->string ) * sizeScale;
    h =	PROP_HEIGHT * sizeScale;

    if( t->generic.flags & QMF_RIGHT_JUSTIFY )
    {
        x -= w;
    }
    else if( t->generic.flags & QMF_CENTER_JUSTIFY )
    {
        x -= w / 2;
    }

    t->generic.left   = x - PROP_GAP_WIDTH * sizeScale;
    t->generic.right  = x + w + PROP_GAP_WIDTH * sizeScale;
    t->generic.top    = y;
    t->generic.bottom = y + h;
}

/*
=================
PText_Draw
=================
*/
static void PText_Draw( menutext_s *t )
{
    int		x;
    int		y;
    float *	color;
    int		style;

    x = t->generic.x;
    y = t->generic.y;

    if (t->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else
        color = t->color;

    style = t->style;
    if( t->generic.flags & QMF_PULSEIFFOCUS )
    {
        if( Menu_ItemAtCursor( t->generic.parent ) == t )
        {
            style |= UI_PULSE;
        }
        else
        {
            style |= UI_INVERSE;
        }
    }

    UI_DrawProportionalString( x, y, t->string, style, color );
}

/*
=================
Bitmap_Init
=================
*/
void Bitmap_Init( menubitmap_s *b )
{
    int	x;
    int	y;
    int	w;
    int	h;

    x = b->generic.x;
    y = b->generic.y;
    w = b->width;
    h =	b->height;
    if( w < 0 )
    {
        w = -w;
    }
    if( h < 0 )
    {
        h = -h;
    }

    if (b->generic.flags & QMF_RIGHT_JUSTIFY)
    {
        x = x - w;
    }
    else if (b->generic.flags & QMF_CENTER_JUSTIFY)
    {
        x = x - w/2;
    }

    b->generic.left   = x;
    b->generic.right  = x + w;
    b->generic.top    = y;
    b->generic.bottom = y + h;

    b->shader      = 0;
    b->focusshader = 0;
}

/*
=================
Bitmap_Draw
=================
*/
void Bitmap_Draw( menubitmap_s *b )
{
    float	x;
    float	y;
    float	w;
    float	h;
    vec4_t	tempcolor;
    float*	color;

    x = b->generic.x;
    y = b->generic.y;
    w = b->width;
    h =	b->height;

    if (b->generic.flags & QMF_RIGHT_JUSTIFY)
    {
        x = x - w;
    }
    else if (b->generic.flags & QMF_CENTER_JUSTIFY)
    {
        x = x - w/2;
    }

    // used to refresh shader
    if (b->generic.name && !b->shader)
    {
        b->shader = RE_RegisterShaderNoMip( b->generic.name );
        if (!b->shader && b->errorpic)
            b->shader = RE_RegisterShaderNoMip( b->errorpic );
    }

    if (b->focuspic && !b->focusshader)
        b->focusshader = RE_RegisterShaderNoMip( b->focuspic );

    if (b->generic.flags & QMF_GRAYED)
    {
        if (b->shader)
        {
            RE_SetColor( colorMdGrey );
            UI_DrawHandlePic( x, y, w, h, b->shader );
            RE_SetColor( NULL );
        }
    }
    else
    {
        if (b->shader)
            UI_DrawHandlePic( x, y, w, h, b->shader );

        // bk001204 - parentheses
        if (  ( (b->generic.flags & QMF_PULSE)
                || (b->generic.flags & QMF_PULSEIFFOCUS) )
                && (Menu_ItemAtCursor( b->generic.parent ) == b))
        {
            if (b->focuscolor)
            {
                tempcolor[0] = b->focuscolor[0];
                tempcolor[1] = b->focuscolor[1];
                tempcolor[2] = b->focuscolor[2];
                color        = tempcolor;
            }
            else
                color = pulse_color;
            color[3] = 0.5+0.5*sin(uis.realtime/PULSE_DIVISOR);

            RE_SetColor( color );
            UI_DrawHandlePic( x, y, w, h, b->focusshader );
            RE_SetColor( NULL );
        }
        else if ((b->generic.flags & QMF_HIGHLIGHT) || ((b->generic.flags & QMF_HIGHLIGHT_IF_FOCUS) && (Menu_ItemAtCursor( b->generic.parent ) == b)))
        {
            if (b->focuscolor)
            {
                RE_SetColor( b->focuscolor );
                UI_DrawHandlePic( x, y, w, h, b->focusshader );
                RE_SetColor( NULL );
            }
            else
                UI_DrawHandlePic( x, y, w, h, b->focusshader );
        }
    }
}

/*
=================
Action_Init
=================
*/
static void Action_Init( menuaction_s *a )
{
    int	len;

    // calculate bounds
    if (a->generic.name)
        len = strlen(a->generic.name);
    else
        len = 0;

    // left justify text
    a->generic.left   = a->generic.x;
    a->generic.right  = a->generic.x + len*BIGCHAR_WIDTH;
    a->generic.top    = a->generic.y;
    a->generic.bottom = a->generic.y + BIGCHAR_HEIGHT;
}

/*
=================
Action_Draw
=================
*/
static void Action_Draw( menuaction_s *a )
{
    int		x, y;
    int		style;
    float*	color;

    style = 0;
    color = menu_text_color;
    if ( a->generic.flags & QMF_GRAYED )
    {
        color = text_color_disabled;
    }
    else if (( a->generic.flags & QMF_PULSEIFFOCUS ) && ( a->generic.parent->cursor == a->generic.menuPosition ))
    {
        color = text_color_highlight;
        style = UI_PULSE;
    }
    else if (( a->generic.flags & QMF_HIGHLIGHT_IF_FOCUS ) && ( a->generic.parent->cursor == a->generic.menuPosition ))
    {
        color = text_color_highlight;
    }
    else if ( a->generic.flags & QMF_BLINK )
    {
        style = UI_BLINK;
        color = text_color_highlight;
    }

    x = a->generic.x;
    y = a->generic.y;

    UI_DrawString( x, y, a->generic.name, UI_LEFT|style, color );

    if ( a->generic.parent->cursor == a->generic.menuPosition )
    {
        // draw cursor
        UI_DrawChar( x - BIGCHAR_WIDTH, y, 13, UI_LEFT|UI_BLINK, color);
    }
}

/*
=================
RadioButton_Init
=================
*/
static void RadioButton_Init( menuradiobutton_s *rb )
{
    int	len;

    // calculate bounds
    if (rb->generic.name)
        len = strlen(rb->generic.name);
    else
        len = 0;

    rb->generic.left   = rb->generic.x - (len+1)*SMALLCHAR_WIDTH;
    rb->generic.right  = rb->generic.x + 6*SMALLCHAR_WIDTH;
    rb->generic.top    = rb->generic.y;
    rb->generic.bottom = rb->generic.y + SMALLCHAR_HEIGHT;
}

/*
=================
RadioButton_Key
=================
*/
static sfxHandle_t RadioButton_Key( menuradiobutton_s *rb, int key )
{
    switch (key)
    {
    case K_MOUSE1:
        if (!(rb->generic.flags & QMF_HASMOUSEFOCUS))
            break;

    case K_JOY1:
    case K_JOY2:
    case K_JOY3:
    case K_JOY4:
    case K_ENTER:
    case K_KP_ENTER:
    case K_KP_LEFTARROW:
    case K_LEFTARROW:
    case K_KP_RIGHTARROW:
    case K_RIGHTARROW:
        rb->curvalue = !rb->curvalue;
        if ( rb->generic.callback )
            rb->generic.callback( rb, QM_ACTIVATED );

        return (menu_move_sound);
    }

    // key not handled
    return 0;
}

/*
=================
RadioButton_Draw
=================
*/
static void RadioButton_Draw( menuradiobutton_s *rb )
{
    int	x;
    int y;
    float *color;
    int	style;
    qboolean focus;

    x = rb->generic.x;
    y = rb->generic.y;

    focus = (rb->generic.parent->cursor == rb->generic.menuPosition);

    if ( rb->generic.flags & QMF_GRAYED )
    {
        color = text_color_disabled;
        style = UI_LEFT|UI_SMALLFONT;
    }
    else if ( focus )
    {
        color = text_color_highlight;
        style = UI_LEFT|UI_PULSE|UI_SMALLFONT;
    }
    else
    {
        color = text_color_normal;
        style = UI_LEFT|UI_SMALLFONT;
    }

    if ( focus )
    {
        // draw cursor
        UI_FillRect( rb->generic.left, rb->generic.top, rb->generic.right-rb->generic.left+1, rb->generic.bottom-rb->generic.top+1, listbar_color );
        UI_DrawChar( x, y, 13, UI_CENTER|UI_BLINK|UI_SMALLFONT, color);
    }

    if ( rb->generic.name )
        UI_DrawString( x - SMALLCHAR_WIDTH, y, rb->generic.name, UI_RIGHT|UI_SMALLFONT, color );

    if ( !rb->curvalue )
    {
        UI_DrawHandlePic( x + SMALLCHAR_WIDTH, y + 2, 16, 16, uis.rb_off);
        UI_DrawString( x + SMALLCHAR_WIDTH + 16, y, "off", style, color );
    }
    else
    {
        UI_DrawHandlePic( x + SMALLCHAR_WIDTH, y + 2, 16, 16, uis.rb_on );
        UI_DrawString( x + SMALLCHAR_WIDTH + 16, y, "on", style, color );
    }
}

/*
=================
Slider_Init
=================
*/
static void Slider_Init( menuslider_s *s )
{
    int len;

    // calculate bounds
    if (s->generic.name)
        len = strlen(s->generic.name);
    else
        len = 0;

    s->generic.left   = s->generic.x - (len+1)*SMALLCHAR_WIDTH;
    s->generic.right  = s->generic.x + (SLIDER_RANGE+2+1)*SMALLCHAR_WIDTH;
    s->generic.top    = s->generic.y;
    s->generic.bottom = s->generic.y + SMALLCHAR_HEIGHT;
}

/*
=================
Slider_Key
=================
*/
static sfxHandle_t Slider_Key( menuslider_s *s, int key )
{
    sfxHandle_t	sound;
    int			x;
    int			oldvalue;

    switch (key)
    {
    case K_MOUSE1:
        x           = uis.cursorx - s->generic.x - 2*SMALLCHAR_WIDTH;
        oldvalue    = s->curvalue;
        s->curvalue = (x/(float)(SLIDER_RANGE*SMALLCHAR_WIDTH)) * (s->maxvalue-s->minvalue) + s->minvalue;

        if (s->curvalue < s->minvalue)
            s->curvalue = s->minvalue;
        else if (s->curvalue > s->maxvalue)
            s->curvalue = s->maxvalue;
        if (s->curvalue != oldvalue)
            sound = menu_move_sound;
        else
            sound = 0;
        break;

    case K_KP_LEFTARROW:
    case K_LEFTARROW:
        if (s->curvalue > s->minvalue)
        {
            s->curvalue--;
            sound = menu_move_sound;
        }
        else
            sound = menu_buzz_sound;
        break;

    case K_KP_RIGHTARROW:
    case K_RIGHTARROW:
        if (s->curvalue < s->maxvalue)
        {
            s->curvalue++;
            sound = menu_move_sound;
        }
        else
            sound = menu_buzz_sound;
        break;

    default:
        // key not handled
        sound = 0;
        break;
    }

    if ( sound && s->generic.callback )
        s->generic.callback( s, QM_ACTIVATED );

    return (sound);
}

#if 1
/*
=================
Slider_Draw
=================
*/
static void Slider_Draw( menuslider_s *s )
{
    int			x;
    int			y;
    int			style;
    float		*color;
    int			button;
    qboolean	focus;

    x =	s->generic.x;
    y = s->generic.y;
    focus = (s->generic.parent->cursor == s->generic.menuPosition);

    if( s->generic.flags & QMF_GRAYED )
    {
        color = text_color_disabled;
        style = UI_SMALLFONT;
    }
    else if( focus )
    {
        color  = text_color_highlight;
        style = UI_SMALLFONT | UI_PULSE;
    }
    else
    {
        color = text_color_normal;
        style = UI_SMALLFONT;
    }

    // draw label
    UI_DrawString( x - SMALLCHAR_WIDTH, y, s->generic.name, UI_RIGHT|style, color );

    // draw slider
    UI_SetColor( color );
    UI_DrawHandlePic( x + SMALLCHAR_WIDTH, y, 96, 16, sliderBar );
    UI_SetColor( NULL );

    // clamp thumb
    if( s->maxvalue > s->minvalue )
    {
        s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );
        if( s->range < 0 )
        {
            s->range = 0;
        }
        else if( s->range > 1)
        {
            s->range = 1;
        }
    }
    else
    {
        s->range = 0;
    }

    // draw thumb
    if( style & UI_PULSE)
    {
        button = sliderButton_1;
    }
    else
    {
        button = sliderButton_0;
    }

    UI_DrawHandlePic( (int)( x + 2*SMALLCHAR_WIDTH + (SLIDER_RANGE-1)*SMALLCHAR_WIDTH* s->range ) - 2, y - 2, 12, 20, button );
}
#else
/*
=================
Slider_Draw
=================
*/
static void Slider_Draw( menuslider_s *s )
{
    float *color;
    int	style;
    int	i;
    int x;
    int y;
    qboolean focus;

    x =	s->generic.x;
    y = s->generic.y;
    focus = (s->generic.parent->cursor == s->generic.menuPosition);

    style = UI_SMALLFONT;
    if ( s->generic.flags & QMF_GRAYED )
    {
        color = text_color_disabled;
    }
    else if (focus)
    {
        color  = text_color_highlight;
        style |= UI_PULSE;
    }
    else
    {
        color = text_color_normal;
    }

    if ( focus )
    {
        // draw cursor
        UI_FillRect( s->generic.left, s->generic.top, s->generic.right-s->generic.left+1, s->generic.bottom-s->generic.top+1, listbar_color );
        UI_DrawChar( x, y, 13, UI_CENTER|UI_BLINK|UI_SMALLFONT, color);
    }

    // draw label
    UI_DrawString( x - SMALLCHAR_WIDTH, y, s->generic.name, UI_RIGHT|style, color );

    // draw slider
    UI_DrawChar( x + SMALLCHAR_WIDTH, y, 128, UI_LEFT|style, color);
    for ( i = 0; i < SLIDER_RANGE; i++ )
        UI_DrawChar( x + (i+2)*SMALLCHAR_WIDTH, y, 129, UI_LEFT|style, color);
    UI_DrawChar( x + (i+2)*SMALLCHAR_WIDTH, y, 130, UI_LEFT|style, color);

    // clamp thumb
    if (s->maxvalue > s->minvalue)
    {
        s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );
        if ( s->range < 0)
            s->range = 0;
        else if ( s->range > 1)
            s->range = 1;
    }
    else
        s->range = 0;

    // draw thumb
    if (style & UI_PULSE)
    {
        style &= ~UI_PULSE;
        style |= UI_BLINK;
    }
    UI_DrawChar( (int)( x + 2*SMALLCHAR_WIDTH + (SLIDER_RANGE-1)*SMALLCHAR_WIDTH* s->range ), y, 131, UI_LEFT|style, color);
}
#endif

/*
=================
SpinControl_Init
=================
*/
static void SpinControl_Init( menulist_s *s )
{
    int	len;
    int	l;
    const char* str;

    if (s->generic.name)
        len = strlen(s->generic.name) * SMALLCHAR_WIDTH;
    else
        len = 0;

    s->generic.left	= s->generic.x - SMALLCHAR_WIDTH - len;

    len = s->numitems = 0;
    while ( (str = s->itemnames[s->numitems]) != 0 )
    {
        l = strlen(str);
        if (l > len)
            len = l;

        s->numitems++;
    }

    s->generic.top	  =	s->generic.y;
    s->generic.right  =	s->generic.x + (len+1)*SMALLCHAR_WIDTH;
    s->generic.bottom =	s->generic.y + SMALLCHAR_HEIGHT;
}

/*
=================
SpinControl_Key
=================
*/
static sfxHandle_t SpinControl_Key( menulist_s *s, int key )
{
    sfxHandle_t	sound;

    sound = 0;
    switch (key)
    {
    case K_MOUSE1:
        s->curvalue++;
        if (s->curvalue >= s->numitems)
            s->curvalue = 0;
        sound = menu_move_sound;
        break;

    case K_KP_LEFTARROW:
    case K_LEFTARROW:
        if (s->curvalue > 0)
        {
            s->curvalue--;
            sound = menu_move_sound;
        }
        else
            sound = menu_buzz_sound;
        break;

    case K_KP_RIGHTARROW:
    case K_RIGHTARROW:
        if (s->curvalue < s->numitems-1)
        {
            s->curvalue++;
            sound = menu_move_sound;
        }
        else
            sound = menu_buzz_sound;
        break;
    }

    if ( sound && s->generic.callback )
        s->generic.callback( s, QM_ACTIVATED );

    return (sound);
}

/*
=================
SpinControl_Draw
=================
*/
static void SpinControl_Draw( menulist_s *s )
{
    float *color;
    int	x,y;
    int	style;
    qboolean focus;

    x = s->generic.x;
    y =	s->generic.y;

    style = UI_SMALLFONT;
    focus = (s->generic.parent->cursor == s->generic.menuPosition);

    if ( s->generic.flags & QMF_GRAYED )
        color = text_color_disabled;
    else if ( focus )
    {
        color = text_color_highlight;
        style |= UI_PULSE;
    }
    else if ( s->generic.flags & QMF_BLINK )
    {
        color = text_color_highlight;
        style |= UI_BLINK;
    }
    else
        color = text_color_normal;

    if ( focus )
    {
        // draw cursor
        UI_FillRect( s->generic.left, s->generic.top, s->generic.right-s->generic.left+1, s->generic.bottom-s->generic.top+1, listbar_color );
        UI_DrawChar( x, y, 13, UI_CENTER|UI_BLINK|UI_SMALLFONT, color);
    }

    UI_DrawString( x - SMALLCHAR_WIDTH, y, s->generic.name, style|UI_RIGHT, color );
    UI_DrawString( x + SMALLCHAR_WIDTH, y, s->itemnames[s->curvalue], style|UI_LEFT, color );
}

/*
=================
ScrollList_Init
=================
*/
static void ScrollList_Init( menulist_s *l )
{
    int		w;

    l->oldvalue = 0;
    l->curvalue = 0;
    l->top      = 0;

    if( !l->columns )
    {
        l->columns = 1;
        l->seperation = 0;
    }
    else if( !l->seperation )
    {
        l->seperation = 3;
    }

    w = ( (l->width + l->seperation) * l->columns - l->seperation) * SMALLCHAR_WIDTH;

    l->generic.left   =	l->generic.x;
    l->generic.top    = l->generic.y;
    l->generic.right  =	l->generic.x + w;
    l->generic.bottom =	l->generic.y + l->height * SMALLCHAR_HEIGHT;

    if( l->generic.flags & QMF_CENTER_JUSTIFY )
    {
        l->generic.left -= w / 2;
        l->generic.right -= w / 2;
    }
}

/*
=================
ScrollList_Key
=================
*/
sfxHandle_t ScrollList_Key( menulist_s *l, int key )
{
    int	x;
    int	y;
    int	w;
    int	i;
    int	j;
    int	c;
    int	cursorx;
    int	cursory;
    int	column;
    int	index;

    switch (key)
    {
    case K_MOUSE1:
        if (l->generic.flags & QMF_HASMOUSEFOCUS)
        {
            // check scroll region
            x = l->generic.x;
            y = l->generic.y;
            w = ( (l->width + l->seperation) * l->columns - l->seperation) * SMALLCHAR_WIDTH;
            if( l->generic.flags & QMF_CENTER_JUSTIFY )
            {
                x -= w / 2;
            }
            if (UI_CursorInRect( x, y, w, l->height*SMALLCHAR_HEIGHT ))
            {
                cursorx = (uis.cursorx - x)/SMALLCHAR_WIDTH;
                column = cursorx / (l->width + l->seperation);
                cursory = (uis.cursory - y)/SMALLCHAR_HEIGHT;
                index = column * l->height + cursory;
                if (l->top + index < l->numitems)
                {
                    l->oldvalue = l->curvalue;
                    l->curvalue = l->top + index;

                    if (l->oldvalue != l->curvalue && l->generic.callback)
                    {
                        l->generic.callback( l, QM_GOTFOCUS );
                        return (menu_move_sound);
                    }
                }
            }

            // absorbed, silent sound effect
            return (menu_null_sound);
        }
        break;

    case K_KP_HOME:
    case K_HOME:
        l->oldvalue = l->curvalue;
        l->curvalue = 0;
        l->top      = 0;

        if (l->oldvalue != l->curvalue && l->generic.callback)
        {
            l->generic.callback( l, QM_GOTFOCUS );
            return (menu_move_sound);
        }
        return (menu_buzz_sound);

    case K_KP_END:
    case K_END:
        l->oldvalue = l->curvalue;
        l->curvalue = l->numitems-1;
        if( l->columns > 1 )
        {
            c = (l->curvalue / l->height + 1) * l->height;
            l->top = c - (l->columns * l->height);
        }
        else
        {
            l->top = l->curvalue - (l->height - 1);
        }
        if (l->top < 0)
            l->top = 0;

        if (l->oldvalue != l->curvalue && l->generic.callback)
        {
            l->generic.callback( l, QM_GOTFOCUS );
            return (menu_move_sound);
        }
        return (menu_buzz_sound);

    case K_PGUP:
    case K_KP_PGUP:
        if( l->columns > 1 )
        {
            return menu_null_sound;
        }

        if (l->curvalue > 0)
        {
            l->oldvalue = l->curvalue;
            l->curvalue -= l->height-1;
            if (l->curvalue < 0)
                l->curvalue = 0;
            l->top = l->curvalue;
            if (l->top < 0)
                l->top = 0;

            if (l->generic.callback)
                l->generic.callback( l, QM_GOTFOCUS );

            return (menu_move_sound);
        }
        return (menu_buzz_sound);

    case K_PGDN:
    case K_KP_PGDN:
        if( l->columns > 1 )
        {
            return menu_null_sound;
        }

        if (l->curvalue < l->numitems-1)
        {
            l->oldvalue = l->curvalue;
            l->curvalue += l->height-1;
            if (l->curvalue > l->numitems-1)
                l->curvalue = l->numitems-1;
            l->top = l->curvalue - (l->height-1);
            if (l->top < 0)
                l->top = 0;

            if (l->generic.callback)
                l->generic.callback( l, QM_GOTFOCUS );

            return (menu_move_sound);
        }
        return (menu_buzz_sound);

    case K_KP_UPARROW:
    case K_UPARROW:
        if( l->curvalue == 0 )
        {
            return menu_buzz_sound;
        }

        l->oldvalue = l->curvalue;
        l->curvalue--;

        if( l->curvalue < l->top )
        {
            if( l->columns == 1 )
            {
                l->top--;
            }
            else
            {
                l->top -= l->height;
            }
        }

        if( l->generic.callback )
        {
            l->generic.callback( l, QM_GOTFOCUS );
        }

        return (menu_move_sound);

    case K_KP_DOWNARROW:
    case K_DOWNARROW:
        if( l->curvalue == l->numitems - 1 )
        {
            return menu_buzz_sound;
        }

        l->oldvalue = l->curvalue;
        l->curvalue++;

        if( l->curvalue >= l->top + l->columns * l->height )
        {
            if( l->columns == 1 )
            {
                l->top++;
            }
            else
            {
                l->top += l->height;
            }
        }

        if( l->generic.callback )
        {
            l->generic.callback( l, QM_GOTFOCUS );
        }

        return menu_move_sound;

    case K_KP_LEFTARROW:
    case K_LEFTARROW:
        if( l->columns == 1 )
        {
            return menu_null_sound;
        }

        if( l->curvalue < l->height )
        {
            return menu_buzz_sound;
        }

        l->oldvalue = l->curvalue;
        l->curvalue -= l->height;

        if( l->curvalue < l->top )
        {
            l->top -= l->height;
        }

        if( l->generic.callback )
        {
            l->generic.callback( l, QM_GOTFOCUS );
        }

        return menu_move_sound;

    case K_KP_RIGHTARROW:
    case K_RIGHTARROW:
        if( l->columns == 1 )
        {
            return menu_null_sound;
        }

        c = l->curvalue + l->height;

        if( c >= l->numitems )
        {
            return menu_buzz_sound;
        }

        l->oldvalue = l->curvalue;
        l->curvalue = c;

        if( l->curvalue > l->top + l->columns * l->height - 1 )
        {
            l->top += l->height;
        }

        if( l->generic.callback )
        {
            l->generic.callback( l, QM_GOTFOCUS );
        }

        return menu_move_sound;
    }

    // cycle look for ascii key inside list items
    if ( !Q_isprint( key ) )
        return (0);

    // force to lower for case insensitive compare
    if ( Q_isupper( key ) )
    {
        key -= 'A' - 'a';
    }

    // iterate list items
    for (i=1; i<=l->numitems; i++)
    {
        j = (l->curvalue + i) % l->numitems;
        c = l->itemnames[j][0];
        if ( Q_isupper( c ) )
        {
            c -= 'A' - 'a';
        }

        if (c == key)
        {
            // set current item, mimic windows listbox scroll behavior
            if (j < l->top)
            {
                // behind top most item, set this as new top
                l->top = j;
            }
            else if (j > l->top+l->height-1)
            {
                // past end of list box, do page down
                l->top = (j+1) - l->height;
            }

            if (l->curvalue != j)
            {
                l->oldvalue = l->curvalue;
                l->curvalue = j;
                if (l->generic.callback)
                    l->generic.callback( l, QM_GOTFOCUS );
                return ( menu_move_sound );
            }

            return (menu_buzz_sound);
        }
    }

    return (menu_buzz_sound);
}

/*
=================
ScrollList_Draw
=================
*/
void ScrollList_Draw( menulist_s *l )
{
    int			x;
    int			u;
    int			y;
    int			i;
    int			base;
    int			column;
    float*		color;
    qboolean	hasfocus;
    int			style;

    hasfocus = (l->generic.parent->cursor == l->generic.menuPosition);

    x =	l->generic.x;
    for( column = 0; column < l->columns; column++ )
    {
        y =	l->generic.y;
        base = l->top + column * l->height;
        for( i = base; i < base + l->height; i++)
        {
            if (i >= l->numitems)
                break;

            if (i == l->curvalue)
            {
                u = x - 2;
                if( l->generic.flags & QMF_CENTER_JUSTIFY )
                {
                    u -= (l->width * SMALLCHAR_WIDTH) / 2 + 1;
                }

                UI_FillRect(u,y,l->width*SMALLCHAR_WIDTH,SMALLCHAR_HEIGHT+2,listbar_color);
                color = text_color_highlight;

                if (hasfocus)
                    style = UI_PULSE|UI_LEFT|UI_SMALLFONT;
                else
                    style = UI_LEFT|UI_SMALLFONT;
            }
            else
            {
                color = text_color_normal;
                style = UI_LEFT|UI_SMALLFONT;
            }
            if( l->generic.flags & QMF_CENTER_JUSTIFY )
            {
                style |= UI_CENTER;
            }

            UI_DrawString(
                x,
                y,
                l->itemnames[i],
                style,
                color);

            y += SMALLCHAR_HEIGHT;
        }
        x += (l->width + l->seperation) * SMALLCHAR_WIDTH;
    }
}

/*
=================
Menu_AddItem
=================
*/
void Menu_AddItem( menuframework_s *menu, void *item )
{
    menucommon_s	*itemptr;

    if (menu->nitems >= MAX_MENUITEMS)
        Com_Error (ERR_DROP, "Menu_AddItem: excessive items");

    menu->items[menu->nitems] = item;
    ((menucommon_s*)menu->items[menu->nitems])->parent        = menu;
    ((menucommon_s*)menu->items[menu->nitems])->menuPosition  = menu->nitems;
    ((menucommon_s*)menu->items[menu->nitems])->flags        &= ~QMF_HASMOUSEFOCUS;

    // perform any item specific initializations
    itemptr = (menucommon_s*)item;
    if (!(itemptr->flags & QMF_NODEFAULTINIT))
    {
        switch (itemptr->type)
        {
        case MTYPE_ACTION:
            Action_Init((menuaction_s*)item);
            break;

        case MTYPE_FIELD:
            MenuField_Init((menufield_s*)item);
            break;

        case MTYPE_SPINCONTROL:
            SpinControl_Init((menulist_s*)item);
            break;

        case MTYPE_RADIOBUTTON:
            RadioButton_Init((menuradiobutton_s*)item);
            break;

        case MTYPE_SLIDER:
            Slider_Init((menuslider_s*)item);
            break;

        case MTYPE_BITMAP:
            Bitmap_Init((menubitmap_s*)item);
            break;

        case MTYPE_TEXT:
            Text_Init((menutext_s*)item);
            break;

        case MTYPE_SCROLLLIST:
            ScrollList_Init((menulist_s*)item);
            break;

        case MTYPE_PTEXT:
            PText_Init((menutext_s*)item);
            break;

        case MTYPE_BTEXT:
            BText_Init((menutext_s*)item);
            break;

        default:
            Com_Error(ERR_DROP, "Menu_Init: unknown type %d", itemptr->type);
        }
    }

    menu->nitems++;
}

/*
=================
Menu_CursorMoved
=================
*/
void Menu_CursorMoved( menuframework_s *m )
{
    void (*callback)( void *self, int notification );

    if (m->cursor_prev == m->cursor)
        return;

    if (m->cursor_prev >= 0 && m->cursor_prev < m->nitems)
    {
        callback = ((menucommon_s*)(m->items[m->cursor_prev]))->callback;
        if (callback)
            callback(m->items[m->cursor_prev],QM_LOSTFOCUS);
    }

    if (m->cursor >= 0 && m->cursor < m->nitems)
    {
        callback = ((menucommon_s*)(m->items[m->cursor]))->callback;
        if (callback)
            callback(m->items[m->cursor],QM_GOTFOCUS);
    }
}

/*
=================
Menu_SetCursor
=================
*/
void Menu_SetCursor( menuframework_s *m, int cursor )
{
    if (((menucommon_s*)(m->items[cursor]))->flags & (QMF_GRAYED|QMF_INACTIVE))
    {
        // cursor can't go there
        return;
    }

    m->cursor_prev = m->cursor;
    m->cursor      = cursor;

    Menu_CursorMoved( m );
}

/*
=================
Menu_SetCursorToItem
=================
*/
void Menu_SetCursorToItem( menuframework_s *m, void* ptr )
{
    int	i;

    for (i=0; i<m->nitems; i++)
    {
        if (m->items[i] == ptr)
        {
            Menu_SetCursor( m, i );
            return;
        }
    }
}

/*
** Menu_AdjustCursor
**
** This function takes the given menu, the direction, and attempts
** to adjust the menu's cursor so that it's at the next available
** slot.
*/
void Menu_AdjustCursor( menuframework_s *m, int dir )
{
    menucommon_s	*item = NULL;
    qboolean		wrapped = qfalse;

wrap:
    while ( m->cursor >= 0 && m->cursor < m->nitems )
    {
        item = ( menucommon_s * ) m->items[m->cursor];
        if (( item->flags & (QMF_GRAYED|QMF_MOUSEONLY|QMF_INACTIVE) ) )
        {
            m->cursor += dir;
        }
        else
        {
            break;
        }
    }

    if ( dir == 1 )
    {
        if ( m->cursor >= m->nitems )
        {
            if ( m->wrapAround )
            {
                if ( wrapped )
                {
                    m->cursor = m->cursor_prev;
                    return;
                }
                m->cursor = 0;
                wrapped = qtrue;
                goto wrap;
            }
            m->cursor = m->cursor_prev;
        }
    }
    else
    {
        if ( m->cursor < 0 )
        {
            if ( m->wrapAround )
            {
                if ( wrapped )
                {
                    m->cursor = m->cursor_prev;
                    return;
                }
                m->cursor = m->nitems - 1;
                wrapped = qtrue;
                goto wrap;
            }
            m->cursor = m->cursor_prev;
        }
    }
}

/*
=================
Menu_Draw
=================
*/
void Menu_Draw( menuframework_s *menu )
{
    int				i;
    menucommon_s	*itemptr;

    // draw menu
    for (i=0; i<menu->nitems; i++)
    {
        itemptr = (menucommon_s*)menu->items[i];

        if (itemptr->flags & QMF_HIDDEN)
            continue;

        if (itemptr->ownerdraw)
        {
            // total subclassing, owner draws everything
            itemptr->ownerdraw( itemptr );
        }
        else
        {
            switch (itemptr->type)
            {
            case MTYPE_RADIOBUTTON:
                RadioButton_Draw( (menuradiobutton_s*)itemptr );
                break;

            case MTYPE_FIELD:
                MenuField_Draw( (menufield_s*)itemptr );
                break;

            case MTYPE_SLIDER:
                Slider_Draw( (menuslider_s*)itemptr );
                break;

            case MTYPE_SPINCONTROL:
                SpinControl_Draw( (menulist_s*)itemptr );
                break;

            case MTYPE_ACTION:
                Action_Draw( (menuaction_s*)itemptr );
                break;

            case MTYPE_BITMAP:
                Bitmap_Draw( (menubitmap_s*)itemptr );
                break;

            case MTYPE_TEXT:
                Text_Draw( (menutext_s*)itemptr );
                break;

            case MTYPE_SCROLLLIST:
                ScrollList_Draw( (menulist_s*)itemptr );
                break;

            case MTYPE_PTEXT:
                PText_Draw( (menutext_s*)itemptr );
                break;

            case MTYPE_BTEXT:
                BText_Draw( (menutext_s*)itemptr );
                break;

            default:
                Com_Error(ERR_DROP, "Menu_Draw: unknown type %d", itemptr->type);
            }
        }
#ifndef NDEBUG
        if( uis.debug )
        {
            int	x;
            int	y;
            int	w;
            int	h;

            if( !( itemptr->flags & QMF_INACTIVE ) )
            {
                x = itemptr->left;
                y = itemptr->top;
                w = itemptr->right - itemptr->left + 1;
                h =	itemptr->bottom - itemptr->top + 1;

                if (itemptr->flags & QMF_HASMOUSEFOCUS)
                {
                    UI_DrawRect(x, y, w, h, colorYellow );
                }
                else
                {
                    UI_DrawRect(x, y, w, h, colorWhite );
                }
            }
        }
#endif
    }

    UI_DrawHandlePic(0, 0, 640, 64, RE_RegisterShaderNoMip("menu/art/menu_topslice.jpg"));

    itemptr = (menucommon_s*)Menu_ItemAtCursor( menu );
    if(itemptr)
    {
        if(itemptr->statusbar)
            itemptr->statusbar( ( void * ) itemptr );
        else if(itemptr->statusText)
            UI_DrawProportionalString_AutoWrapped(320, 0, 640, 32, itemptr->statusText, UI_CENTER | UI_SMALLFONT, colorWhite);
    }
}

/*
=================
Menu_ItemAtCursor
=================
*/
void *Menu_ItemAtCursor( menuframework_s *m )
{
    if ( m->cursor < 0 || m->cursor >= m->nitems )
        return 0;

    return m->items[m->cursor];
}

/*
=================
Menu_ActivateItem
=================
*/
sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item )
{
    if ( item->callback )
    {
        item->callback( item, QM_ACTIVATED );
        if( !( item->flags & QMF_SILENT ) )
        {
            return menu_move_sound;
        }
    }

    return 0;
}

/*
=================
Menu_DefaultKey
=================
*/
sfxHandle_t Menu_DefaultKey( menuframework_s *m, int key )
{
    sfxHandle_t		sound = 0;
    menucommon_s	*item;
    int				cursor_prev;

    // menu system keys
    switch ( key )
    {
    case K_MOUSE2:
    case K_ESCAPE:
        UI_PopMenu();
        return menu_out_sound;
    }

    if (!m || !m->nitems)
        return 0;

    // route key stimulus to widget
    item = (menucommon_s*)Menu_ItemAtCursor( m );
    if (item && !(item->flags & (QMF_GRAYED|QMF_INACTIVE)))
    {
        switch (item->type)
        {
        case MTYPE_SPINCONTROL:
            sound = SpinControl_Key( (menulist_s*)item, key );
            break;

        case MTYPE_RADIOBUTTON:
            sound = RadioButton_Key( (menuradiobutton_s*)item, key );
            break;

        case MTYPE_SLIDER:
            sound = Slider_Key( (menuslider_s*)item, key );
            break;

        case MTYPE_SCROLLLIST:
            sound = ScrollList_Key( (menulist_s*)item, key );
            break;

        case MTYPE_FIELD:
            sound = MenuField_Key( (menufield_s*)item, &key );
            break;
        }

        if (sound)
        {
            // key was handled
            return sound;
        }
    }

    // default handling
    switch ( key )
    {
#ifndef NDEBUG
    case K_F11:
        uis.debug ^= 1;
        break;

    case K_F12:
        Cbuf_ExecuteText(EXEC_APPEND, "screenshot\n");
        break;
#endif
    case K_KP_UPARROW:
    case K_UPARROW:
        cursor_prev    = m->cursor;
        m->cursor_prev = m->cursor;
        m->cursor--;
        Menu_AdjustCursor( m, -1 );
        if ( cursor_prev != m->cursor )
        {
            Menu_CursorMoved( m );
            sound = menu_move_sound;
        }
        break;

    case K_TAB:
    case K_KP_DOWNARROW:
    case K_DOWNARROW:
        cursor_prev    = m->cursor;
        m->cursor_prev = m->cursor;
        m->cursor++;
        Menu_AdjustCursor( m, 1 );
        if ( cursor_prev != m->cursor )
        {
            Menu_CursorMoved( m );
            sound = menu_move_sound;
        }
        break;

    case K_MOUSE1:
    case K_MOUSE3:
        if (item)
            if ((item->flags & QMF_HASMOUSEFOCUS) && !(item->flags & (QMF_GRAYED|QMF_INACTIVE)))
                return (Menu_ActivateItem( m, item ));
        break;

    case K_JOY1:
    case K_JOY2:
    case K_JOY3:
    case K_JOY4:
    case K_AUX1:
    case K_AUX2:
    case K_AUX3:
    case K_AUX4:
    case K_AUX5:
    case K_AUX6:
    case K_AUX7:
    case K_AUX8:
    case K_AUX9:
    case K_AUX10:
    case K_AUX11:
    case K_AUX12:
    case K_AUX13:
    case K_AUX14:
    case K_AUX15:
    case K_AUX16:
    case K_KP_ENTER:
    case K_ENTER:
        if (item)
            if (!(item->flags & (QMF_MOUSEONLY|QMF_GRAYED|QMF_INACTIVE)))
                return (Menu_ActivateItem( m, item ));
        break;
    }

    return sound;
}

/*
=================
Menu_Cache
=================
*/
void Menu_Cache( void )
{
    uis.charset			= RE_RegisterShaderNoMip( "hud/2d/bigchars" );
    uis.charsetProp		= RE_RegisterShaderNoMip( "menu/art/font1_prop.tga" );
    uis.charsetPropGlow	= RE_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" );
    uis.charsetPropB	= RE_RegisterShaderNoMip( "menu/art/font2_prop.tga" );
    uis.cursor          = RE_RegisterShaderNoMip( "menu/art/cursor_pointer" );
    uis.rb_on           = RE_RegisterShaderNoMip( "menu/art/switch_on" );
    uis.rb_off          = RE_RegisterShaderNoMip( "menu/art/switch_off" );

    uis.whiteShader = RE_RegisterShaderNoMip( "white" );
	uis.menuBackShader	= RE_RegisterShaderNoMip( "menuback" );

    menu_in_sound	= S_RegisterSound( "sound/misc/menu1.wav", qfalse );
    menu_move_sound	= S_RegisterSound( "sound/misc/menu2.wav", qfalse );
    menu_out_sound	= S_RegisterSound( "sound/misc/menu3.wav", qfalse );
    menu_buzz_sound	= S_RegisterSound( "sound/misc/menu4.wav", qfalse );
    weaponChangeSound	= S_RegisterSound( "sound/weapons/change.wav", qfalse );

    // need a nonzero sound, make an empty sound for this
    menu_null_sound = -1;

    sliderBar = RE_RegisterShaderNoMip( "menu/art/slider2" );
    sliderButton_0 = RE_RegisterShaderNoMip( "menu/art/sliderbutt_0" );
    sliderButton_1 = RE_RegisterShaderNoMip( "menu/art/sliderbutt_1" );
}

